package contract

import (
	"context"
	"crypto/ecdsa"
	"encoding/hex"
	"fmt"
	"github.com/coming-chat/wallet-SDK/core/base"
	"github.com/coming-chat/wallet-SDK/core/eth"
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/lunny/log"
	"math/big"
	"strconv"
	"strings"
	"time"
)

type ConnUrl string

const (
	Matic_Url ConnUrl = "https://rpc-mumbai.maticvigil.com/"
	chainId           = 80001
)

type Wallet struct {
	privateKey string
	Address    string
	timeout    time.Duration
	chainId    *big.Int

	*eth.EthChain
}

func newWallet(privateKey string, wallet *eth.EthChain, connUrl ConnUrl) *Wallet {
	if wallet != nil {
		wallet.CreateRemote(string(connUrl))
	}
	address, err := eth.PrivateKeyToAddress(privateKey)
	if err != nil {
		panic(err)
	}
	return &Wallet{
		privateKey: privateKey,
		Address:    address,
		EthChain:   wallet,
		timeout:    time.Second * 3,
		chainId:    new(big.Int).SetInt64(chainId),
	}
}

type Contract struct {
	abi       string
	contrAddr string
	rpcUrl    ConnUrl

	*Wallet
}

// NewContract New contractTest instance contains account info and contractTest info and conn info.
func NewContract(
	abi string,
	addr string,
	wallet *Wallet,
) *Contract {
	return &Contract{
		abi:       abi,
		contrAddr: addr,
		Wallet:    wallet,
	}
}

// ContractInvoke Invoke contract method
func (c *Contract) InvokeQuery(method string, result interface{}, params ...interface{}) error {
	err := c.CallContractConstant(
		&result,
		c.contrAddr,
		c.abi,
		method,
		nil,
		params...,
	)
	if err != nil {
		return err
	}
	return nil
}

// InvokeTx Invoke contract transaction method
func (c *Contract) InvokeTx(method string, opts *eth.CallMethodOpts, params ...interface{}) (string, error) {
	//nonce, _ := wallet.Nonce(c.Address)
	//nonceInt, _ := strconv.ParseInt(nonce, 10, 64)
	//// 构造多笔交易则nonce + 1
	//callMethodOpts := &eth.CallMethodOpts{
	//	Nonce: nonceInt,
	//}

	buildTxResult, err := c.BuildCallMethodTx(
		method,
		opts,
		params...,
	)
	if err != nil {
		return "", err
	}

	txHash, err := c.SendRawTransaction(buildTxResult.TxHex)
	if err != nil {
		return "", err
	}

	return txHash, nil
}

// 对合约进行调用
func (c *Contract) BuildCallMethodTx(
	methodName string, opts *eth.CallMethodOpts, params ...interface{}) (*eth.BuildTxResult, error) {

	privateKey = strings.TrimPrefix(c.privateKey, "0x")

	parsedAbi, err := abi.JSON(strings.NewReader(c.abi))
	if err != nil {
		return nil, err
	}
	contractAddressObj := common.HexToAddress(c.contrAddr)
	privateKeyBuf, err := hex.DecodeString(privateKey)
	if err != nil {
		return nil, err
	}
	optsBigInt := eth.OptsTobigInt(opts)

	var nonce uint64 = 0
	var value = big.NewInt(0)
	var gasPrice = big.NewInt(0)
	var gasLimit uint64 = 0
	var isPredictError = true
	var maxPriorityFeePerGas = big.NewInt(0)
	_, _, _ = isPredictError, gasPrice, maxPriorityFeePerGas
	if opts != nil {
		// A nonce is a sequential number assigned to each transaction originating from a specific Ethereum address.
		// It prevents replay attacks and ensures that each transaction is processed only once. The nonce starts at
		// 0 for the first transaction and increments by 1 for each subsequent transaction.
		nonce = optsBigInt.Nonce
		// The amount of Ether (or tokens) being sent in the transaction.
		value = optsBigInt.Value
		// The price (in Wei, the smallest denomination of Ether) the sender is willing to pay per unit of gas.
		// It determines the transaction fee.
		gasPrice = optsBigInt.GasPrice
		// The maximum amount of gas units that can be consumed by the transaction. It represents the maximum
		// computational work that can be performed for the transaction. If the actual gas consumption exceeds
		// this limit, the transaction fails, but unused gas is refunded.
		gasLimit = optsBigInt.GasLimit
		// A boolean flag indicating whether there was an error in predicting gas usage or fees for the transaction.
		isPredictError = opts.IsPredictError
		// Introduced in Ethereum's EIP-1559, MaxPriorityFeePerGas is the maximum tip (in Wei per unit of gas) the
		//sender is willing to pay for the miner to prioritize including the transaction in the block. It's part of
		//the new fee market mechanism aiming to improve fee estimation and transaction prioritization.
		maxPriorityFeePerGas = optsBigInt.MaxPriorityFeePerGas
	}

	privateKeyECDSA, err := crypto.ToECDSA(privateKeyBuf)
	if err != nil {
		return nil, err
	}
	publicKeyECDSA := privateKeyECDSA.PublicKey
	fromAddress := crypto.PubkeyToAddress(publicKeyECDSA)
	if nonce == 0 {
		ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
		defer cancel()
		nonce, err = c.RemoteRpcClient.PendingNonceAt(ctx, fromAddress)
		if err != nil {
			return nil, fmt.Errorf("failed to retrieve account nonce: %v", err)
		}
	}

	if params == nil || len(params) < 1 {
		params = []interface{}{}
	}

	// 将string地址类型转化为hex类型

	input, err := parsedAbi.Pack(methodName,
		params...)
	if err != nil {
		return nil, err
	}

	if gasLimit == 0 || isPredictError {
		msg := ethereum.CallMsg{
			//  The Ethereum address (sender) initiating the transaction.
			From: fromAddress,

			// The Ethereum address (recipient) receiving the transaction.
			To: &contractAddressObj,

			// The amount of gas units allocated for executing the transaction.
			// Gas is used to power operations on the Ethereum network, and each operation consumes a certain amount of gas.
			//Gas: 0,

			// The price (in Wei, the smallest denomination of Ether)
			// the sender is willing to pay per unit of gas. This determines the transaction fee.
			//GasPrice: price,

			// The maximum price (in Wei per unit of gas) the sender is willing to pay
			// for the transaction. It's particularly relevant for transactions using the EIP-1559 fee market mechanism
			//introduced in Ethereum London hard fork. It sets a ceiling on the gas fee the sender is willing to pay.
			//GasFeeCap: price,

			// The maximum tip (in Wei per unit of gas) the sender is willing to pay for the miner to prioritize
			//including the transaction in the block. Also part of the EIP-1559 fee market mechanism.
			//GasTipCap: maxPriorityFeePerGas,

			// The amount of Ether (or tokens) being sent in the transaction.
			Value: value,

			//  Optional data that can be included in a transaction, often used when interacting with smart contracts.
			Data: input,

			// A feature introduced in Ethereum Berlin hard fork aimed at optimizing gas usage for certain types of
			// interactions with contracts, particularly beneficial for contract-to-contract interactions.
			AccessList: nil,
		}
		tempGasLimit, err := estimateGasLimit(c, msg, c.timeout)
		if err != nil {
			return nil, fmt.Errorf("failed to estimate gas: %v", err)
		}
		tempGasLimitUint, err := strconv.ParseUint(tempGasLimit, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("failed parse estimate gas: %v", err)
		}
		gasLimit = uint64(float64(tempGasLimitUint) * 2)
	}

	return c.buildTx(privateKeyECDSA, nonce, contractAddressObj, value, gasLimit, input, opts)
}

func estimateGasLimit(c *Contract, msg ethereum.CallMsg, timeout time.Duration) (gas string, err error) {
	defer base.CatchPanicAndMapToBasicError(&err)
	gas = eth.DEFAULT_ETH_GAS_LIMIT

	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	gasCount, err := c.RemoteRpcClient.EstimateGas(ctx, msg)
	if err != nil {
		return
	}

	gasLimitStr := strconv.FormatUint(gasCount, 10)
	return gasLimitStr, nil
}

func (c *Contract) buildTx(
	privateKeyECDSA *ecdsa.PrivateKey,
	nonce uint64,
	toAddressObj common.Address,
	value *big.Int,
	gasLimit uint64,
	data []byte,
	opts *eth.CallMethodOpts) (*eth.BuildTxResult, error) {
	var rawTx *types.Transaction

	if nonce == 0 && opts.Nonce == 0 {
		nonceVal, err := wallet.Nonce(c.Address)
		if err != nil {
			return nil, err
		}
		opts.Nonce, err = strconv.ParseInt(nonceVal, 10, 64)
		if err != nil {
			return nil, err
		}
	}

	optsBigInt := eth.OptsTobigInt(opts)
	if nonce == 0 {
		nonce = optsBigInt.Nonce
	}

	if optsBigInt.MaxPriorityFeePerGas == nil {
		var gasPrice *big.Int = nil
		if opts != nil && optsBigInt.GasPrice != nil {
			gasPrice = optsBigInt.GasPrice
		}
		if gasPrice == nil {
			ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
			defer cancel()
			_gasPrice, err := c.RemoteRpcClient.SuggestGasPrice(ctx)
			if err != nil {
				return nil, fmt.Errorf("failed to suggest gas price: %v", err)
			}
			gasPrice = _gasPrice
		}

		rawTx = types.NewTx(&types.LegacyTx{
			Nonce:    nonce,
			To:       &toAddressObj,
			Value:    value,
			Gas:      gasLimit,
			GasPrice: gasPrice,
			Data:     data,
		})
	} else {
		rawTx = types.NewTx(&types.DynamicFeeTx{
			Nonce:     nonce,
			To:        &toAddressObj,
			Value:     value,
			Gas:       gasLimit,
			GasFeeCap: optsBigInt.GasPrice,             // maxFeePerGas 最大的 gasPrice（包含 baseFee），减去 baseFee 就是小费。gasPrice = min(maxFeePerGas, baseFee + maxPriorityFeePerGas)
			GasTipCap: optsBigInt.MaxPriorityFeePerGas, // maxPriorityFeePerGas，也就是最大的小费。GasTipCap 和 gasFeeCap - baseFee 的更小值才是真正的给矿工的，baseFee 是销毁的。
			Data:      data,
		})
	}
	return buildTxWithTransaction(c, rawTx, privateKeyECDSA)
}

func buildTxWithTransaction(c *Contract, transaction *types.Transaction, privateKeyCDSA *ecdsa.PrivateKey) (*eth.BuildTxResult, error) {
	signedTx, err := types.SignTx(transaction, types.LatestSignerForChainID(c.chainId), privateKeyCDSA)
	if err != nil {
		return nil, err
	}
	txBytes, err := signedTx.MarshalBinary()
	if err != nil {
		return nil, err
	}
	return &eth.BuildTxResult{
		SignedTx: signedTx,
		TxHex:    hexutil.Encode(txBytes),
	}, nil
}

func (e *Wallet) TransactionByHash(txHash string) (*eth.TransactionByHashResult, error) {
	ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
	defer cancel()
	tx, isPending, err := e.RemoteRpcClient.TransactionByHash(ctx, common.HexToHash(txHash))
	if err != nil {
		return nil, err
	}
	msg, err := tx.AsMessage(types.NewEIP155Signer(e.chainId), nil)
	if err != nil {
		log.Warn(err)
	}

	// 交易receipt 状态信息，0表示失败，1表示成功
	receipt, err := e.TransactionReceiptByHash(txHash)
	var status, gasUsed, blockNumber string
	// 当交易没有处于pending状态时，可以查询receipt信息，即交易是否成功, err为nil时，表示查询成功进入if语句赋值
	if err == nil {
		gasUsed = strconv.FormatUint(receipt.GasUsed, 10)
		status = strconv.FormatUint(receipt.Status, 10)
		blockNumber = receipt.BlockHash.String()
	}

	return &eth.TransactionByHashResult{
		tx,
		msg.From(),
		isPending,
		status,
		gasUsed,
		blockNumber,
	}, nil
}
